Skip to content

[LMCache] Add LMCache arm on tuned DSV4 FP4 B300 vLLM AgentX recipe / 在调优后的 DSV4 FP4 B300 vLLM AgentX 配方上新增 LMCache 分支#2232

Open
ApostaC wants to merge 2 commits into
mainfrom
lmcache-vllm-b300
Open

[LMCache] Add LMCache arm on tuned DSV4 FP4 B300 vLLM AgentX recipe / 在调优后的 DSV4 FP4 B300 vLLM AgentX 配方上新增 LMCache 分支#2232
ApostaC wants to merge 2 commits into
mainfrom
lmcache-vllm-b300

Conversation

@ApostaC

@ApostaC ApostaC commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Combines the tuned DeepSeek-V4 FP4 B300 vLLM AgentX recipe from #2225 with the LMCache KV-offload backend from #2153, mirroring the B200 arm in #2231, so the LMCache arm runs on the same image and serving flags as the vllm-simple and Mooncake arms and the backends are directly comparable.

Validated locally: bash -n passes; python -m pytest utils/matrix_logic/ -q — 221 passed; generate_sweep_configs.py full-sweep emits all 17 lmcache points (router attached, total-cpu-dram-gb resolved from dram-utilization: 0.80); validate_perf_changelog.py against origin/main passes. Not yet run on hardware — needs a sweep run. One thing to watch in bring-up: the B300 recipe runs --gpu-memory-utilization 0.96 (vs 0.92 on B200), which could be tight with LMCache's GPU-side IPC mappings.

中文说明

#2225 中调优后的 DeepSeek-V4 FP4 B300 vLLM AgentX 配方与 #2153 的 LMCache KV 卸载后端合并(对应 B200 的 #2231),使 LMCache 分支与 vllm-simple、Mooncake 分支在相同镜像和相同 serving 参数下运行,便于卸载后端间直接对比。

本地验证:bash -n 通过;python -m pytest utils/matrix_logic/ -q — 221 项测试通过;generate_sweep_configs.py full-sweep 生成全部 17 个 lmcache 测试点(带 router,total-cpu-dram-gbdram-utilization: 0.80 解析);validate_perf_changelog.py 对比 origin/main 校验通过。尚未在硬件上运行——需要触发扫描。调试期间需关注:B300 配方使用 --gpu-memory-utilization 0.96(B200 为 0.92),叠加 LMCache 的 GPU 侧 IPC 映射后余量可能偏紧。

Related Issue

Builds on #2225 (tuned B300 AgentX recipe), #2153 (LMCache backend), and #2231 (the B200 counterpart of this PR). / 基于 #2225(B300 AgentX 调优配方)、#2153(LMCache 后端)与 #2231(本 PR 的 B200 对应版本)。

Type of Change

  • Bug fix
  • New feature
  • Configuration change
  • Documentation update
  • Other (please describe)

Checklist

  • I have tested my changes locally
  • I have updated documentation if necessary
  • If I changed a container image or config, I have already updated perf-changelog.yaml
    • New perf-changelog.yaml entries are appended to the end of the file (the file is chronological: oldest at top, newest at bottom)
  • Before merging via reuse, an authorized maintainer (OWNER/MEMBER/COLLABORATOR) has commented /reuse-sweep-run on this PR — do this only once there is a final full sweep that is all green with evals passing, since after this comment the sweep label will no longer automatically kick off new sweeps (remove and re-add the label to force one)

🤖 Generated with Claude Code

Combine the tuned B300 recipe from PR #2225 (nightly image, sparse DSV4
FlashInfer attention, FULL_DECODE_ONLY CUDA graphs, AMXF4 mega-MoE) with
the LMCache 0.5.1 KV-offload backend from PR #2153, mirroring the B200
arm in PR #2231. The lmcache arm keeps the stock PyTorch caching
allocator instead of PYTORCH_ALLOC_CONF=expandable_segments:True
(expandable-segment cuMem/VMM allocations cannot be CUDA-IPC-exported to
the LMCache MP server, the same failure mode as --enable-cumem-allocator
on B200) and runs otherwise identical serving flags, so backends are
directly comparable. Adds a standalone dsv4-fp4-b300-vllm-agentic-lmcache
config section mirroring the vllm-simple DEP4/DEP8 concurrency ladders
and a perf-changelog entry triggering it.

中文:将 PR #2225 的 B300 调优配方(nightly 镜像、稀疏 DSV4 FlashInfer
注意力、FULL_DECODE_ONLY CUDA graph、AMXF4 mega-MoE)与 PR #2153 的
LMCache 0.5.1 KV 卸载后端合并,对应 B200 的 PR #2231。lmcache 分支仅以
默认 PyTorch 缓存分配器替代 PYTORCH_ALLOC_CONF=expandable_segments:True
(expandable segments 的 cuMem/VMM 分配无法通过 CUDA IPC 导出给 LMCache
MP server,与 B200 上 --enable-cumem-allocator 的失败模式相同),其余
serving 参数与其他分支保持一致,便于卸载后端间直接对比。新增独立的
dsv4-fp4-b300-vllm-agentic-lmcache 配置(测试点与 vllm-simple 的
DEP4/DEP8 阶梯对齐)及触发它的 perf-changelog 条目。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the contribution! Please reach out to respective companies' CODEOWNER to fill in the latest PR_REVIEW_CHECKLIST.md before pinging core maintainer on Slack for review. In order for the signoff PR check bot to trigger, you must follow the PR_REVIEW_CHECKLIST.md template correctly, including the phrase As a PR reviewer and CODEOWNER, I have reviewed this and have.

For PR verification, add the full-sweep-fail-fast label (strongly recommended) to this PR — the benchmark sweep only runs on labeled PRs. Use full-sweep-enabled only if you need matrix jobs to keep running past a failure.

PR authors are responsible for ensuring that after merging, all GitHub Action jobs fully pass. A lot of the time, failures are just flakes and simply re-running the failed jobs will fix it. See GitHub's docs on re-running failed jobs


感谢你的贡献!请联系相应公司的 CODEOWNER 填写最新的 PR_REVIEW_CHECKLIST.md,然后再在 Slack 上联系核心维护者进行审阅。为了触发 signoff PR 检查机器人,你必须正确遵循 PR_REVIEW_CHECKLIST.md 模板,包括保留英文语句 As a PR reviewer and CODEOWNER, I have reviewed this and have

如需进行 PR 验证,请为此 PR 添加 full-sweep-fail-fast 标签(强烈推荐)— 基准测试 sweep 仅在带有标签的 PR 上运行。仅当需要矩阵任务在失败后继续运行时才使用 full-sweep-enabled

PR 作者有责任确保合并后所有 GitHub Action 任务完全通过。 很多时候失败只是偶发抖动(flake),重新运行失败的任务即可解决。参见 GitHub 关于重新运行失败任务的文档

中文:将 perf-changelog 条目的 pr-link 填写为 PR #2232。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread perf-changelog.yaml Outdated
- "LMCache MP server (lmcache_driven transfer mode) + LMCacheMPConnector per PR #2153/#2231; L1 pool derated to 75% of TOTAL_CPU_DRAM_GB; PYTORCH_ALLOC_CONF=expandable_segments:True dropped on the lmcache arm only (VMM allocations cannot be CUDA-IPC-exported to the LMCache server, same failure mode as cuMem on B200)"
- "Points mirror the vllm-simple ladders: TP4-DEP4 conc [8, 16, 24, 32, 40, 48, 56, 64, 72]; TP8-DEP8 conc [32, 64, 96, 128, 160, 192, 224, 228]"
- "Parent dsv4-fp4-b300-vllm-agentic updated in the same change to the PR #2225 recipe and search space (image bump, vllm-simple + Mooncake arms) so the shared script stays consistent; the parent points are benchmarked by PR #2225 and not re-triggered here"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/XXXX

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The new perf-changelog.yaml entry's pr-link uses pull/XXXX (four X's), but validate_perf_changelog.py's PR_LINK_PLACEHOLDERS only recognizes XXX (three X's) or the real canonical PR link. This mismatch means the placeholder will never get canonicalized to the real PR link during the merge-with-reuse flow. Fix by changing it to pull/XXX (three X's) or the real PR number (2232).

Extended reasoning...

The appended entry in perf-changelog.yaml (line 4827) sets:

pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/XXXX

four X characters. utils/validate_perf_changelog.py defines:

CANONICAL_PR_LINK = re.compile(r"https://github.com/SemiAnalysisAI/InferenceX/pull/\d+")
PR_LINK_PLACEHOLDERS = {
    "XXX",
    "https://github.com/SemiAnalysisAI/InferenceX/pull/XXX",
}

Both of these expect exactly three X's, not four. pull/XXXX therefore matches neither the canonical digit-based link pattern nor the placeholder set.

Step-by-step proof:

  1. validate_added_pr_link() is invoked with link = "https://github.com/SemiAnalysisAI/InferenceX/pull/XXXX".
  2. If pr_number is None (e.g., a main-branch validation run), the code requires CANONICAL_PR_LINK.fullmatch(link). The regex requires \d+ after pull/; XXXX is not digits, so fullmatch returns None and the check fails.
  3. If pr_number is set (a PR-run, e.g. 2232), the code computes expected = ".../pull/2232" and checks link not in PR_LINK_PLACEHOLDERS and link != expected. "https://github.com/SemiAnalysisAI/InferenceX/pull/XXXX" is not in PR_LINK_PLACEHOLDERS (which only contains the three-X forms) and is not equal to expected, so the condition is true and ChangelogValidationError("new PR entry must use ... or an XXX placeholder; found ...") is raised.
  4. Either way, the placeholder as written is rejected.

This check is reached at merge time via prepare_perf_changelog_merge.py's canonicalize/resolve-conflict subcommands, which utils/merge_with_reuse.sh invokes with the real --pr-number to rewrite placeholder links to the real PR URL. Because compare_entries()validate_added_pr_link() rejects pull/XXXX outright, that canonicalization step will fail, meaning the reuse-merge tooling cannot resolve this entry to the real PR link and the merge flow breaks for this changelog entry. (Separately, the PR-time run-sweep.yml CI invocation of validate_perf_changelog.py only runs validate_generated_config(), which does not call this comparison path, so it will not itself fail at PR-review time — but the PR description's claim that "validate_perf_changelog.py against origin/main passes" is not evidence this placeholder is valid, since that path isn't exercised by the local check either.)

The fix is a one-character edit: change pull/XXXX to pull/XXX (three X's) to match the accepted placeholder, or simply write the real PR number 2232 directly.

Comment on lines 254 to +259
unset VLLM_USE_SIMPLE_KV_OFFLOAD
OFFLOAD_ARGS=(
--kv-transfer-config
'{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_connector_extra_config":{"load_async":true}}'
"{\"kv_connector\":\"LMCacheMPConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{\"lmcache.mp.host\":\"$LMCACHE_CONNECT_HOST\",\"lmcache.mp.port\":$LMCACHE_PORT,\"lmcache.mp.mq_timeout\":$LMCACHE_MQ_TIMEOUT}}"
)
fi
;;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The new LMCache arm's --kv-transfer-config sets kv_connector":"LMCacheMPConnector" but omits kv_connector_module_path, unlike the two sibling scripts (dsv4_fp4_mi355x_vllm.sh:344, kimik2.5_fp4_b200.sh:150) that wire up the same connector. Since LMCacheMPConnector is out-of-tree, vLLM will not be able to locate the class and vllm serve should fail at KV-connector construction, breaking every point in the new dsv4-fp4-b300-vllm-agentic-lmcache config. Fix by adding "kv_connector_module_path":"lmcache.integration.vllm.lmcache_mp_connector" to the config dict.

Extended reasoning...

The bug: In the lmcache) case of benchmarks/single_node/agentic/dsv4_fp4_b300_vllm.sh (around lines 254-259), OFFLOAD_ARGS is built as:

\n"{\"kv_connector\":\"LMCacheMPConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{...}}"\n

This JSON blob is missing the kv_connector_module_path key.

Why it matters: LMCacheMPConnector is not a vLLM built-in connector (unlike MooncakeStoreConnector, which vLLM resolves through its internal connector-factory registry). It lives in the external lmcache package, at lmcache.integration.vllm.lmcache_mp_connector. vLLM's KVTransferConfig resolves out-of-tree connectors by dynamically importing the module named in kv_connector_module_path before looking up the connector class by name — without that field, vLLM has no way to find the class.

Confirmed by direct comparison with sibling scripts: I checked the two other scripts in this repo that wire up the identical LMCacheMPConnector:

  • benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm.sh:344: "kv_connector\":\"LMCacheMPConnector\",\"kv_connector_module_path\":\"lmcache.integration.vllm.lmcache_mp_connector\"...
  • benchmarks/single_node/agentic/kimik2.5_fp4_b200.sh:150 (also lmcache 0.5.1, matching this PR's version exactly): same pattern.

Both sibling scripts explicitly set kv_connector_module_path alongside kv_connector. This new B300 arm is the only one of the three that omits it.

Why nothing else catches this: The script does run python3 -c \"import lmcache.integration.vllm.lmcache_mp_connector\" earlier, but that only verifies the module is importable in the launcher process — it says nothing to vLLM about where to import the connector class from when it parses --kv-transfer-config. bash -n, the matrix-logic pytest suite, and generate_sweep_configs.py all validate shell syntax and config-generation logic, not the semantics of the JSON payload passed to vLLM, so none of them would catch this.

Step-by-step proof of the failure:

  1. KV_OFFLOAD_BACKEND=lmcache is selected, LMCache MP server starts and passes its healthcheck.
  2. OFFLOAD_ARGS is set to --kv-transfer-config '{\"kv_connector\":\"LMCacheMPConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{...}}' (no kv_connector_module_path).
  3. vllm serve parses this into a KVTransferConfig. Since kv_connector_module_path is absent, vLLM falls back to its built-in connector-factory lookup by name.
  4. LMCacheMPConnector is not registered there (it is only importable via the external lmcache package), so the lookup fails and vLLM raises an error during KV-connector construction, before the server can start serving.
  5. Every concurrency point in the new dsv4-fp4-b300-vllm-agentic-lmcache search space (17 points total) uses this same code path, so the entire new config fails at server startup, not just some points.

Fix: add \"kv_connector_module_path\":\"lmcache.integration.vllm.lmcache_mp_connector\" to the JSON dict at the same spot the two sibling scripts do, e.g.:

\n"{\"kv_connector\":\"LMCacheMPConnector\",\"kv_connector_module_path\":\"lmcache.integration.vllm.lmcache_mp_connector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{...}}"\n

Severity is normal, not a nit or pre-existing issue: this is new code introduced by this PR, and merging as-is causes a concrete, deterministic startup failure for the entire new lmcache arm — not a stylistic or description mismatch.

@@ -1,60 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
set -eo pipefail

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Line 2 weakens set -euo pipefail to set -eo pipefail, dropping nounset — this is the only script among 23 in benchmarks/single_node/agentic/ that does so (its B200 sibling dsv4_fp4_b200_vllm.sh from #2231 handles the exact same optionally-unset vars with ${VAR:-} instead). Recommend restoring set -u and using ${KV_OFFLOAD_BACKEND:-}, ${SLURM_JOB_ID:-}, and ${MODEL_PATH:-} in the new unguarded expansions instead of disabling nounset for the whole ~350-line script.

Extended reasoning...

What changed: Line 2 of dsv4_fp4_b300_vllm.sh changes set -euo pipefail to set -eo pipefail, dropping the -u (nounset) flag. I verified this is the only script among all 23 scripts in benchmarks/single_node/agentic/ that lacks nounset — every other script, including the direct B200 counterpart dsv4_fp4_b200_vllm.sh introduced in #2231 (which this PR explicitly says it mirrors), keeps set -euo pipefail.

Why it was dropped: The rewrite replaces the old declare -p VAR >/dev/null 2>&1 && [ -n "$VAR" ] guards for MODEL_PATH/SLURM_JOB_ID with plain [[ -n "$VAR" ]], and adds a new case "$KV_OFFLOAD_BACKEND" in "") branch that relies on KV_OFFLOAD_BACKEND expanding to empty when unset. None of KV_OFFLOAD_BACKEND, SLURM_JOB_ID, or MODEL_PATH are in the check_env_vars allowlist, so under nounset these bare references would abort with "unbound variable" whenever the var isn't exported (e.g. a stand-alone run without SLURM, or the vllm-simple/mooncake KV_OFFLOAD_BACKEND path). Dropping -u script-wide is the blunt fix for that.

Why this is unnecessary: The B200 sibling script that this PR is explicitly mirroring solves the identical problem without sacrificing nounset, using the standard ${VAR:-} parameter-expansion idiom: [[ -n "${SLURM_JOB_ID:-}" ]], ${SLURMD_NODENAME:-unknown}, [[ -n "${MODEL_PATH:-}" ]]. benchmark_lib.sh itself uses this idiom throughout. So the intent (let three specific optional vars expand to empty) can be fully achieved without disabling the safety net for the other ~350 lines of the script — the fix conflated "these three vars are allowed to be unset" with "disable unbound-variable checking everywhere."

Impact: With nounset off, any future typo in a variable reference anywhere in this script (e.g. a misspelled $LMCACHE_PORT, $TOTAL_CPU_DRAM_GB, or $GPU_COUNT) will silently expand to an empty string instead of aborting immediately with "unbound variable." Concretely: GPU_COUNT=$TP further down is guarded by a regex check so a typo there would still be caught, but a hypothetical typo inside one of the arithmetic expressions (e.g. LMCACHE_L1_SIZE_GB=$((TOTAL_CPU_DRAM_GB * 3 / 4)) misspelled as TOTAL_CPU_DRM_GB) would silently evaluate to 0 rather than failing fast, producing a confusing downstream failure (or a nonsensical LMCache pool size) instead of an immediate, obvious error at the point of the typo.

Step-by-step proof of the regression mechanism (not a live failure today, since check_env_vars still validates required vars and the arithmetic guards catch $GPU_COUNT):

  1. Suppose a future edit misspells $TOTAL_CPU_DRAM_GB as $TOTAL_CPU_DRAM_G inside the lmcache branch's LMCACHE_L1_SIZE_GB=$((TOTAL_CPU_DRAM_GB * 3 / 4)) line.
  2. Under set -u (the previous/sibling-script behavior), bash would immediately abort with TOTAL_CPU_DRAM_G: unbound variable, pointing directly at the typo.
  3. Under this PR's set -eo pipefail (nounset disabled), the misspelled var silently expands to empty string, the arithmetic becomes $(( * 3 / 4 )) → bash arithmetic treats an empty operand as 0, so LMCACHE_L1_SIZE_GB=0.
  4. The script proceeds to start lmcache server --l1-size-gb 0 ..., which either fails deep inside the LMCache server startup logs (unclear connection to the root cause) or silently runs a degenerate 0-size cache — either way, a much harder failure to root-cause than an immediate bash error at the typo site.

Fix: Restore set -euo pipefail on line 2, and change the three unguarded references to use parameter expansion consistent with the B200 sibling: [[ -n "${SLURM_JOB_ID:-}" ]], ${SLURMD_NODENAME:-unknown}, [[ -n "${MODEL_PATH:-}" ]], and case "${KV_OFFLOAD_BACKEND:-}" in "").

On severity: All four verifiers independently confirmed the factual claim (only script in the directory without nounset, unnecessary given the ${VAR:-} idiom is used successfully in the sibling script) but converged on nit rather than normal, and I agree. check_env_vars still validates every genuinely required variable, GPU_COUNT is separately regex-validated, and the script runs correctly today as written on every current sweep point — the harm is a hypothetical future maintenance hazard, not a concrete failure in this PR. That's a real, worth-fixing consistency/robustness regression, but not a merge blocker.

@ApostaC ApostaC added agentx AgentX benchmarks, recipes, and infrastructure full-sweep-enabled labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentx AgentX benchmarks, recipes, and infrastructure full-sweep-enabled

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant